package com.srmt.feishu.client.impl;

import com.lark.oapi.Client;
import com.lark.oapi.service.docx.v1.model.*;
import com.lark.oapi.service.drive.v1.model.*;
import com.srmt.feishu.client.FeishuClient;
import com.srmt.feishu.client.DocxService;
import com.srmt.feishu.client.DriveService;
import com.srmt.feishu.client.SheetsService;
import com.srmt.common.ratelimit.RateLimiter;
import com.srmt.common.ratelimit.config.RateLimitConfig;
import com.srmt.common.ratelimit.exception.RateLimitException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

/**
 * 限流的飞书客户端实现
 *
 * 在调用飞书SDK之前应用限流策略，支持API级别的QPS限制和重试机制
 *
 * <h3>主要功能：</h3>
 * <ul>
 *   <li>API级别的限流控制：支持为不同API配置独立的QPS限制</li>
 *   <li>重试机制：在达到限流阈值时自动重试</li>
 *   <li>服务隔离：为不同服务（文档、云盘、通讯录）提供独立的限流实现</li>
 *   <li>配置灵活：支持全局默认配置和API特定配置</li>
 * </ul>
 *
 * <h3>重构说明：</h3>
 * <ul>
 *   <li>消除了165行重复代码，提取共享的限流方法applyRateLimit()</li>
 *   <li>内部类改为实例类，可访问外部类的共享方法</li>
 *   <li>使用ConfigAdapter统一配置转换逻辑</li>
 * </ul>
 *
 * @author SRMT Development Team
 * @since 1.0.0
 * @version 2.0.0 重构消除重复代码
 */
public class RateLimitedFeishuClient implements FeishuClient {
    
    private static final Logger logger = LoggerFactory.getLogger(RateLimitedFeishuClient.class);
    
    private final Client delegate;
    private final RateLimiter rateLimiter;
    private final RateLimitConfig config;
    private final RateLimitedDocxService docxService;
    private final RateLimitedDriveService driveService;
    private final RateLimitedSheetsService sheetsService;
    private final FeishuClient innerClient;
    
    /**
     * 构造函数
     *
     * @param delegate 原始的飞书SDK客户端
     * @param rateLimiter 限流器（可以为null，表示不限流）
     * @param config 限流配置
     */
    public RateLimitedFeishuClient(Client delegate, RateLimiter rateLimiter, RateLimitConfig config) {
        if (delegate == null) {
            throw new IllegalArgumentException("客户端代理对象不能为null");
        }
        if (config == null) {
            throw new IllegalArgumentException("限流配置对象不能为null");
        }
        
        this.delegate = delegate;
        this.rateLimiter = rateLimiter;
        this.config = config;
        
        // 创建内部的默认客户端用于非限流的服务
        this.innerClient = new DefaultFeishuClient(delegate);

        this.docxService = new RateLimitedDocxService(delegate, rateLimiter, config);
        this.driveService = new RateLimitedDriveService(delegate, rateLimiter, config);
        this.sheetsService = new RateLimitedSheetsService(innerClient.sheets(), rateLimiter, config);

        if (rateLimiter != null && config.isEnabled()) {
            logger.info("限流飞书客户端已初始化,限流已启用");
        } else {
            logger.info("限流飞书客户端已初始化,限流已禁用");
        }
    }
    
    @Override
    public DocxService docx() {
        return docxService;
    }
    
    @Override
    public DriveService drive() {
        return driveService;
    }

    
    @Override
    public SheetsService sheets() {
        return sheetsService;
    }
    
    @Override
    public void shutdown() {
        logger.info("正在关闭限流飞书客户端");
        if (rateLimiter != null) {
            rateLimiter.shutdown();
        }
    }

    /**
     * 共享的限流方法
     * 在调用API之前应用限流策略
     *
     * @param api API名称
     * @throws RateLimitException 如果达到限流阈值
     */
    private void applyRateLimit(String api) {
        // 如果限流器为null或配置禁用，直接返回
        if (rateLimiter == null || !config.isEnabled()) {
            logger.debug("[限流] API {} 的限流已禁用或未配置", api);
            return;
        }

        RateLimitConfig.ApiConfig apiConfig = config.getApiConfig(api);
        if (!apiConfig.isEnabled()) {
            logger.debug("[限流] API {} 的限流已明确禁用", api);
            return;
        }

        long startTime = System.currentTimeMillis();
        long retryTimeout = apiConfig.getRetryTimeout();
        int maxRetries = apiConfig.getMaxRetries();
        int qps = apiConfig.getQps();

        logger.debug("[限流] 为API {} 应用限流,QPS: {},超时: {}ms,最大重试: {}",
                    api, qps, retryTimeout, maxRetries);

        boolean acquired = false;
        int attempts = 0;

        while (attempts < maxRetries && !acquired) {
            attempts++;
            logger.debug("[限流] API: {} - 尝试 {}/{}, calling tryAcquire with timeout: {}ms",
                        api, attempts, maxRetries, retryTimeout);

            if (rateLimiter.tryAcquire(api, retryTimeout)) {
                acquired = true;
                long elapsedTime = System.currentTimeMillis() - startTime;
                int remainingTokens = rateLimiter.getAvailablePermits(api);
                if (attempts > 1) {
                    logger.info("[限流] API: {} - 经过 {} 次尝试后获取令牌成功,耗时: {}ms,剩余令牌: {}",
                               api, attempts, elapsedTime, remainingTokens);
                } else {
                    logger.debug("[限流] API: {} - 立即获取令牌成功,剩余令牌: {}",
                                api, remainingTokens);
                }
            } else if (attempts < maxRetries) {
                logger.warn("[限流] API: {} - 获取令牌失败,尝试 {}/{},将重试...",
                           api, attempts, maxRetries);
            }
        }

        if (!acquired) {
            int finalQps = rateLimiter.getQps(api);
            long waitTime = 1000 / Math.max(1, finalQps);
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.error("[限流] API: {} - 超出速率限制!经过 {} 次尝试和 {}ms 后失败。QPS: {},建议等待: {}ms",
                        api, attempts, elapsedTime, finalQps, waitTime);
            throw new RateLimitException(api, finalQps, waitTime);
        }
    }

    /**
     * 限流的文档服务实现
     */
    private class RateLimitedDocxService implements DocxService {
        private final DocxService delegate;

        RateLimitedDocxService(Client client, RateLimiter rateLimiter, RateLimitConfig config) {
            this.delegate = innerClient.docx();
        }

        @Override
        public Document createDocument(CreateDocumentReq req) throws Exception {
            applyRateLimit("docxDocumentCreate");
            return delegate.createDocument(req);
        }

        @Override
        public Document getDocument(GetDocumentReq req) throws Exception {
            applyRateLimit("docxDocumentGet");
            return delegate.getDocument(req);
        }

        @Override
        public Block getDocumentBlock(GetDocumentBlockReq req) throws Exception {
            applyRateLimit("docxDocumentBlockGet");
            return delegate.getDocumentBlock(req);
        }

        @Override
        public Block[] listDocumentBlock(ListDocumentBlockReq req) throws Exception {
            applyRateLimit("docxDocumentBlockList");
            return delegate.listDocumentBlock(req);
        }

        @Override
        public java.util.List<Block> listAllDocumentBlocks(String documentId) throws Exception {
            applyRateLimit("docxDocumentBlockListAll");
            return delegate.listAllDocumentBlocks(documentId);
        }

        @Override
        public Block patchDocumentBlock(PatchDocumentBlockReq req) throws Exception {
            applyRateLimit("docxDocumentBlockPatch");
            return delegate.patchDocumentBlock(req);
        }

        @Override
        public CreateDocumentBlockChildrenRespBody createDocumentBlockChildren(CreateDocumentBlockChildrenReq req) throws Exception {
            applyRateLimit("docxDocumentBlockChildrenCreate");
            return delegate.createDocumentBlockChildren(req);
        }

        @Override
        public CreateDocumentBlockDescendantRespBody createDocumentBlockDescendant(CreateDocumentBlockDescendantReq req) throws Exception {
            applyRateLimit("docxDocumentBlockDescendantCreate");
            return delegate.createDocumentBlockDescendant(req);
        }

        @Override
        public BatchUpdateDocumentBlockRespBody batchUpdateDocumentBlock(BatchUpdateDocumentBlockReq req) throws Exception {
            applyRateLimit("docxDocumentBlockBatchUpdate");
            return delegate.batchUpdateDocumentBlock(req);
        }
    }
    
    /**
     * 限流的云盘服务实现
     */
    private class RateLimitedDriveService implements DriveService {
        private final DriveService delegate;

        RateLimitedDriveService(Client client, RateLimiter rateLimiter, RateLimitConfig config) {
            this.delegate = innerClient.drive();
        }

        @Override
        public String copyFile(CopyFileReq req) throws Exception {
            applyRateLimit("driveFileCopy");
            return delegate.copyFile(req);
        }

        @Override
        public void deleteFile(DeleteFileReq req) throws Exception {
            applyRateLimit("driveFileDelete");
            delegate.deleteFile(req);
        }

        @Override
        public BaseMember createPermissionMember(CreatePermissionMemberReq req) throws Exception {
            applyRateLimit("drivePermissionMemberCreate");
            return delegate.createPermissionMember(req);
        }

        @Override
        public BaseMember updatePermissionMember(UpdatePermissionMemberReq req) throws Exception {
            applyRateLimit("drivePermissionMemberUpdate");
            return delegate.updatePermissionMember(req);
        }

        @Override
        public void deletePermissionMember(DeletePermissionMemberReq req) throws Exception {
            applyRateLimit("drivePermissionMemberDelete");
            delegate.deletePermissionMember(req);
        }

        @Override
        public Member[] listPermissionMember(ListPermissionMemberReq req) throws Exception {
            applyRateLimit("drivePermissionMemberList");
            return delegate.listPermissionMember(req);
        }
    }

    /**
     * 限流的电子表格服务实现
     */
    private class RateLimitedSheetsService implements SheetsService {
        private final SheetsService delegate;

        RateLimitedSheetsService(SheetsService delegate, RateLimiter rateLimiter, RateLimitConfig config) {
            this.delegate = delegate;
        }

        // ========== 基础信息查询 (v3 API - 已重构) ==========

        @Override
        public com.lark.oapi.service.sheets.v3.model.Sheet getSheet(
                String spreadsheetToken, String sheetId) throws Exception {
            applyRateLimit("sheetsSpreadsheetSheetGet");
            return delegate.getSheet(spreadsheetToken, sheetId);
        }

        @Override
        public com.lark.oapi.service.sheets.v3.model.GetSpreadsheet getSpreadsheet(
                String spreadsheetToken) throws Exception {
            applyRateLimit("sheetsSpreadsheetGet");
            return delegate.getSpreadsheet(spreadsheetToken);
        }

        @Override
        public com.lark.oapi.service.sheets.v3.model.Sheet[] querySheets(
                String spreadsheetToken) throws Exception {
            applyRateLimit("sheetsSpreadsheetSheetQuery");
            return delegate.querySheets(spreadsheetToken);
        }

        @Override
        public com.lark.oapi.service.sheets.v3.model.Spreadsheet createSpreadsheet(
                com.lark.oapi.service.sheets.v3.model.CreateSpreadsheetReq req) throws Exception {
            applyRateLimit("sheetsSpreadsheetCreate");
            return delegate.createSpreadsheet(req);
        }

        // ========== 数据读写操作 ==========

        @Override
        public java.util.List<java.util.List<String>> getValues(
                String spreadsheetToken, String range) throws Exception {
            applyRateLimit("sheetsValuesGet");
            return delegate.getValues(spreadsheetToken, range);
        }

        @Override
        public boolean updateValues(String spreadsheetToken, String range,
                                   java.util.List<java.util.List<String>> values) throws Exception {
            applyRateLimit("sheetsValuesUpdate");
            return delegate.updateValues(spreadsheetToken, range, values);
        }

        @Override
        public boolean batchUpdateValues(String spreadsheetToken,
                                        java.util.Map<String, java.util.List<java.util.List<String>>> updates)
                                        throws Exception {
            applyRateLimit("sheetsValuesBatchUpdate");
            return delegate.batchUpdateValues(spreadsheetToken, updates);
        }

        @Override
        public boolean prependValues(String spreadsheetToken, String range,
                                    java.util.List<java.util.List<String>> values) throws Exception {
            applyRateLimit("sheetsValuesPrepend");
            return delegate.prependValues(spreadsheetToken, range, values);
        }

        @Override
        public boolean appendValues(String spreadsheetToken, String range,
                                   java.util.List<java.util.List<String>> values) throws Exception {
            applyRateLimit("sheetsValuesAppend");
            return delegate.appendValues(spreadsheetToken, range, values);
        }

        // ========== 维度操作（行列） ==========

        @Override
        public boolean addDimensionRange(String spreadsheetToken, String sheetId,
                                        String dimension, int length) throws Exception {
            applyRateLimit("sheetsDimensionRangeAdd");
            return delegate.addDimensionRange(spreadsheetToken, sheetId, dimension, length);
        }

        @Override
        public boolean insertDimensionRange(String spreadsheetToken, String sheetId,
                                           String dimension, int startIndex, int endIndex) throws Exception {
            applyRateLimit("sheetsDimensionRangeInsert");
            return delegate.insertDimensionRange(spreadsheetToken, sheetId, dimension, startIndex, endIndex);
        }

        @Override
        public boolean deleteDimensionRange(String spreadsheetToken, String sheetId,
                                           String dimension, int startIndex, int endIndex) throws Exception {
            applyRateLimit("sheetsDimensionRangeDelete");
            return delegate.deleteDimensionRange(spreadsheetToken, sheetId, dimension, startIndex, endIndex);
        }

        @Override
        public boolean updateDimensionRange(String spreadsheetToken, String sheetId,
                                           String dimension, int startIndex, int endIndex, int size)
                                           throws Exception {
            applyRateLimit("sheetsDimensionRangeUpdate");
            return delegate.updateDimensionRange(spreadsheetToken, sheetId, dimension, startIndex, endIndex, size);
        }

        // ========== 单元格操作 ==========

        @Override
        public boolean mergeCells(String spreadsheetToken, String sheetId,
                                 int startRow, int startCol, int endRow, int endCol) throws Exception {
            applyRateLimit("sheetsMergeCells");
            return delegate.mergeCells(spreadsheetToken, sheetId, startRow, startCol, endRow, endCol);
        }

        @Override
        public boolean unmergeCells(String spreadsheetToken, String sheetId,
                                   int startRow, int startCol, int endRow, int endCol) throws Exception {
            applyRateLimit("sheetsUnmergeCells");
            return delegate.unmergeCells(spreadsheetToken, sheetId, startRow, startCol, endRow, endCol);
        }

        @Override
        public boolean setCellStyle(String spreadsheetToken, String range, Object style) throws Exception {
            applyRateLimit("sheetsCellStyleSet");
            return delegate.setCellStyle(spreadsheetToken, range, style);
        }

        @Override
        public boolean setDataValidation(String spreadsheetToken, String range, Object validation) throws Exception {
            applyRateLimit("sheetsDataValidationSet");
            return delegate.setDataValidation(spreadsheetToken, range, validation);
        }

        // ========== 保护范围操作 ==========

        @Override
        public boolean addProtectedDimension(String spreadsheetToken,
                                            java.util.List<com.srmt.feishu.client.model.ProtectedDimension> protectedDimensions,
                                            String userIdType) throws Exception {
            applyRateLimit("sheetsProtectedDimensionAdd");
            return delegate.addProtectedDimension(spreadsheetToken, protectedDimensions, userIdType);
        }
    }
}